﻿//_______________________________________________________________________________________________________________________
// Copy Right (c) 2010, 2011 SapiensIT Consulting GmbH, all rights reserved
//_______________________________________________________________________________________________________________________
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT
// NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//_______________________________________________________________________________________________________________________
// PROJECT:     sapiens.at.SharePoint.QueryBuilder (SharePoint 2010/V4)
//_______________________________________________________________________________________________________________________
// VERSION:     2.0
//_______________________________________________________________________________________________________________________
// DESCRIPTION: 
//_______________________________________________________________________________________________________________________
// LICENSE:     Different licens Types apply to different file types:
//               - Source code files are governed by the MICROSOFT PUBLIC LICENSE (Ms-PL)
//               - Binary files are governed by MSDN CODE GALLERY BINARY LICENSE
//               - Documentation files are governed by CREATIVE COMMONS ATTRIBUTION 3.0 LICENSE
//
//               The licenses can be found at the projects source location.
//               If you do not agree, don't use it!
// 
//_______________________________________________________________________________________________________________________
// Start SharePoint Branding, visit http://www.cliggin.com! Visit us on http://www.sapiens.at! Tell your friends! 
//_______________________________________________________________________________________________________________________
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace Sapiens.at.SharePoint.Utils
{
    /// <summary>
    /// Methods to create and modify <see cref="SPQuery"/> objects.
    /// </summary>
    public static class QueryBuilder
    {
        #region SPQuery Extension Methods

        /// <summary>
        /// Sets the ViewFields of the query.
        /// </summary>
        /// <param name="query"></param>
        /// <param name="fields">the internal names of the fields</param>
        /// <returns>Returns the modified query to perform additional functions</returns>
        public static SPQuery Select(this SPQuery query, params string[] fields)
        {
            query.ViewFields = GetFieldRefCollection(fields);
            return query;
        }

        /// <summary>
        /// Alias for <see cref="Select"/>.
        /// </summary>
        /// <returns>Returns the modified query to perform additional functions</returns>
        public static SPQuery ViewFields(this SPQuery query, params string[] fields)
        {
            return query.Select(fields);
        }
        
        /// <summary>
        /// Sets the <see cref="SPQuery.Query"/> property of the query with the result of the provided <paramref name="expression"/>.
        /// </summary>
        /// <param name="query">The query to modify</param>
        /// <param name="expression">The expression to filter the result of the query</param>
        /// <returns>Returns the modified query to perform additional functions</returns>
        public static SPQuery Where(this SPQuery query, IQueryBuilderExpression expression)
        {
            return query.Where(expression, false);
        }

        /// <summary>
        /// Sets the <see cref="SPQuery.Query"/> property of the query with the result of the provided <paramref name="expression"/>.
        /// </summary>
        /// <param name="query">The query to modify</param>
        /// <param name="expression">The expression to filter the result of the query</param>
        /// <param name="reuseExistingWhere">true, to keep an already existing where clause and nest it into the provided expression; otherwise false</param>
        /// <returns>Returns the modified query to perform additional functions</returns>
        public static SPQuery Where(this SPQuery query, IQueryBuilderExpression expression, bool reuseExistingWhere)
        {
            if (expression != null && !expression.IsEmpty())
            {
                var regex = new Regex(@"\<Where\>.*\</Where\>", RegexOptions.IgnoreCase);
                if (regex.IsMatch(query.Query ?? string.Empty))
                {
                    var currentWhere = regex.Match(query.Query).ToString();
                    var newWhere = expression.ToString();
                    if (reuseExistingWhere)
                    {
                        newWhere = string.Format("<Where><And>{0}{1}</And></Where>", currentWhere.Replace("<Where>","").Replace("</Where>",""), newWhere);
                    }
                    query.Query = regex.Replace(query.Query, string.Format("<Where>{0}</Where>", newWhere));
                }
                else
                {
                    query.Query += string.Format("<Where>{0}</Where>", expression.ToString());
                }
            }
            return query;
        }

        /// <summary>
        /// Adds or replaces an "OrderBy" node of the query.
        /// </summary>
        /// <param name="query"></param>
        ///<param name="expressions">Expressions to order the result of the quer</param>
        /// <returns>Returns the modified query to perform additional functions</returns>
        public static SPQuery OrderBy(this SPQuery query, params QueryBuilderOrderByOperator[] expressions)
        {
            if (expressions != null)
            {
                string OrderByExpression = "";
                foreach (QueryBuilderOrderByOperator expression in expressions)
                {
                    OrderByExpression += expression.ToString();
                }
                query.Query += string.Format("<OrderBy>{0}</OrderBy>", OrderByExpression);
            }
            return query;
        }
        /// <summary>
        /// Adds or replaces an "OrderBy" node of the query.
        /// </summary>
        /// <param name="query"></param>
        /// <param name="fields">The fields to order the result of the query</param>
        /// <returns>Returns the modified query to perform additional functions</returns>
        public static SPQuery OrderBy(this SPQuery query, params string[] fields)
        {
            return AddOrReplaceFieldRefs(query, "OrderBy", GetFieldRefCollection(fields));
        }

        /// <summary>
        /// Adds or replaces a "GroupBy" node of the query.
        /// </summary>
        /// <param name="query"></param>
        /// <param name="fields">The fields to group the result of the query</param>
        /// <returns>Returns the modified query to perform additional functions</returns>
        public static SPQuery GroupBy(this SPQuery query, params string[] fields)
        {
            return AddOrReplaceFieldRefs(query, "GroupBy", GetFieldRefCollection(fields));
        }

        /// <summary>
        /// Sets or replaces the search scope of the query.
        /// </summary>
        /// <param name="query"></param>
        /// <param name="scope"></param>
        /// <returns></returns>
        public static SPQuery Scope(this SPQuery query, SPViewScope scope)
        {
            var result = string.Format("Scope=\"{0}\"", Enum.GetName(typeof(SPViewScope), scope));
            var regex = new Regex("Scope=\".*\"", RegexOptions.IgnoreCase);
            if (regex.IsMatch(query.ViewAttributes ?? ""))
            { // There is allready a scope defined -> replace
                query.ViewAttributes = regex.Replace(query.ViewAttributes, result); 
            }
            else
            { // No scope defined yet -> insert
                query.ViewAttributes = string.Format("{0} {1}", query.ViewAttributes, result).Trim();
            }
            return query;
        }

        #endregion
        /// <summary>
        /// Instantiates a new <see cref="SPQuery"/> and sets the Query property of the query
        /// with the result of the provided <paramref name="expression"/>.
        /// </summary>
        /// <remarks>
        /// Alternatively you can use the <see cref="Where(SPQuery, IQueryBuilderExpression)"/> extension method.
        /// </remarks>
        /// <param name="expression">The expression to filter the result of the query</param>
        /// <returns>The new query</returns>
        public static SPQuery Where(IQueryBuilderExpression expression)
        {
            return new SPQuery().Where(expression);
        }

        /// <summary>
        /// Instantiates a new <see cref="SPQuery"/> and sets the Query property of the query
        /// with the result of the provided <paramref name="expression"/>.
        /// </summary>
        /// <remarks>
        /// Alternatively you can use the <see cref="Where(SPQuery, IQueryBuilderExpression)"/> extension method.
        /// </remarks>
        /// <param name="expression">The expression to filter the result of the query</param>
        /// <param name="view">The view to instantiate the query with</param>
        /// <returns>The new query</returns>
        public static SPQuery Where(IQueryBuilderExpression expression, SPView view)
        {
            return new SPQuery(view).Where(expression);
        }

        /// <summary>
        /// Returns a new connective operator representing the <c>&lt;And&gt;</c> in a CAML query.
        /// </summary>
        /// <param name="exps">The expressions to combine with the operator. Unlike CAML you can pass a variable amount of expressions which are going to
        /// be nested for the output.</param>
        /// <returns>The expression</returns>
        public static QueryBuilderConnective And(params IQueryBuilderExpression[] exps)
        {
            return new QueryBuilderConnective("And", exps);
        }

        /// <summary>
        /// Returns a new connective operator representing the <c>&lt;Or&gt;</c> in a CAML query.
        /// </summary>
        /// <param name="exps">The expressions to combine with the operator. Unlike CAML you can pass a variable amount of expressions which are going to
        /// be nested for the output.</param>
        /// <returns>The expression</returns>
        public static QueryBuilderConnective Or(params IQueryBuilderExpression[] exps)
        {
            return new QueryBuilderConnective("Or", exps);
        }

        /// <summary>
        /// Returns a new equal expression.
        /// </summary>
        /// <remarks>
        /// To compare a lookup field with an ID use <see cref="EqLookup"/> instead.
        /// </remarks>
        /// <typeparam name="T">The type of the value to compare</typeparam>
        /// <param name="fieldRef">The name of the field</param>
        /// <param name="value">The value to compare</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Eq<T>(string fieldRef, T value)
        {
            return new QueryBuilderValueOperator<T>("Eq", fieldRef, value);
        }

        /// <summary>
        /// Returns a new equal expression that compares an ID with the value of a lookup field.
        /// </summary>
        /// <param name="fieldRef">The name of the lookup field</param>
        /// <param name="value">The ID to compare</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression EqLookup(string fieldRef, int value)
        {
            return new QueryBuilderValueOperator<int>("Eq", fieldRef, value, true);
        }
        /// <summary>
        /// Returns a new equal expression that compares DateTime values
        /// </summary>
        /// <param name="fieldRef">The name of the datetime field</param>
        /// <param name="value">The DateTime value to compare</param>
        /// <param name="IncludeTimeValue">Indicates whether the time part is included in the query</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Eq(string fieldRef, DateTime value, bool IncludeTimeValue)
        {
            return new QueryBuilderDateTimeValueOperator("Eq", fieldRef, value, IncludeTimeValue);
        }
        /// <summary>
        /// Returns a new not-equal expression.
        /// </summary>
        /// <remarks>
        /// To compare a lookup field with an ID use <see cref="NeqLookup"/> instead.
        /// </remarks>
        /// <typeparam name="T">The type of the value to compare</typeparam>
        /// <param name="fieldRef">The name of the field</param>
        /// <param name="value">The value to compare</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Neq<T>(string fieldRef, T value)
        {
            return new QueryBuilderValueOperator<T>("Neq", fieldRef, value);
        }

        /// <summary>
        /// Returns a new not-equal expression that compares an ID with the value of a lookup field.
        /// </summary>
        /// <param name="fieldRef">The name of the lookup field</param>
        /// <param name="value">The ID to compare</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression NeqLookup(string fieldRef, int value)
        {
            return new QueryBuilderValueOperator<int>("Neq", fieldRef, value, true);
        }

        /// <summary>
        /// Returns a new not-equal expression that compares DateTime values
        /// </summary>
        /// <param name="fieldRef">The name of the datetime field</param>
        /// <param name="value">The DateTime value to compare</param>
        /// <param name="IncludeTimeValue">Indicates whether the time part is included in the query</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Neq(string fieldRef, DateTime value, bool IncludeTimeValue)
        {
            return new QueryBuilderDateTimeValueOperator("Neq", fieldRef, value, IncludeTimeValue);
        }

        /// <summary>
        /// Returns a new begins-with expression.
        /// </summary>
        /// <param name="fieldRef">The name of the field</param>
        /// <param name="value">The value to compare</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression BeginsWith(string fieldRef, string value)
        {
            if (string.IsNullOrEmpty(value)) { return null; }
            return new QueryBuilderValueOperator<string>("BeginsWith", fieldRef, value);
        }

        /// <summary>
        /// Returns a new contains expression.
        /// </summary>
        /// <param name="fieldRef">The name of the field</param>
        /// <param name="value">The value to compare</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Contains(string fieldRef, string value)
        {
            if (string.IsNullOrEmpty(value)) { return null; }
            return new QueryBuilderValueOperator<string>("Contains", fieldRef, value);
        }

        /// <summary>
        /// Returns a new greater-then-or-equal expression.
        /// </summary>
        /// <param name="fieldRef">The name of the field</param>
        /// <param name="value">The value to compare</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Geq<T>(string fieldRef, T value)
        {
            return new QueryBuilderValueOperator<T>("Geq", fieldRef, value);
        }

        /// <summary>
        /// Returns a new greater-then-or-equal expression that compares DateTime values.
        /// </summary>
        /// <param name="fieldRef">The name of the DateTime field</param>
        /// <param name="value">The value to compare</param>
        /// <param name="IncludeTimeValue">Indicates whether the time part is included in the query</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Geq(string fieldRef, DateTime value, bool IncludeTimeValue)
        {
            return new QueryBuilderDateTimeValueOperator("Geq", fieldRef, value, IncludeTimeValue);
        }

        /// <summary>
        /// Returns a new greater-then expression.
        /// </summary>
        /// <param name="fieldRef">The name of the field</param>
        /// <param name="value">The value to compare</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Gt<T>(string fieldRef, T value)
        {
            return new QueryBuilderValueOperator<T>("Gt", fieldRef, value);
        }

        /// <summary>
        /// Returns a new greater-then expression that compares DateTime values.
        /// </summary>
        /// <param name="fieldRef">The name of the DateTime field</param>
        /// <param name="value">The value to compare</param>
        /// <param name="IncludeTimeValue">Indicates whether the time part is included in the query</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Gt(string fieldRef, DateTime value, bool IncludeTimeValue)
        {
            return new QueryBuilderDateTimeValueOperator("Gt", fieldRef, value, IncludeTimeValue);
        }
        

        /// <summary>
        /// Return a new lower-then-or-equal expression.
        /// </summary>
        /// <param name="fieldRef">The name of the field</param>
        /// <param name="value">The value to compare</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Leq<T>(string fieldRef, T value)
        {
            return new QueryBuilderValueOperator<T>("Leq", fieldRef, value);
        }

        /// <summary>
        /// Returns a new lower-then-or-equal expression that compares DateTime values.
        /// </summary>
        /// <param name="fieldRef">The name of the DateTime field</param>
        /// <param name="value">The value to compare</param>
        /// <param name="IncludeTimeValue">Indicates whether the time part is included in the query</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Leq(string fieldRef, DateTime value, bool IncludeTimeValue)
        {
            return new QueryBuilderDateTimeValueOperator("Leq", fieldRef, value, IncludeTimeValue);
        }

        /// <summary>
        /// Return a new lower-then expression.
        /// </summary>
        /// <param name="fieldRef">The name of the field</param>
        /// <param name="value">The value to compare</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Lt<T>(string fieldRef, T value)
        {
            return new QueryBuilderValueOperator<T>("Lt", fieldRef, value);
        }

        /// <summary>
        /// Returns a new lower-then expression that compares DateTime values.
        /// </summary>
        /// <param name="fieldRef">The name of the DateTime field</param>
        /// <param name="value">The value to compare</param>
        /// <param name="IncludeTimeValue">Indicates whether the time part is included in the query</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression Lt(string fieldRef, DateTime value, bool IncludeTimeValue)
        {
            return new QueryBuilderDateTimeValueOperator("Lt", fieldRef, value, IncludeTimeValue);
        }

        /// <summary>
        /// Return a new expression that checks if a field is not null.
        /// </summary>
        /// <remarks>
        /// If you want to check if a lookupfield is not null use <see cref="IsNotNullLookup"/>.
        /// </remarks>
        /// <param name="fieldRef">The name of the field</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression IsNotNull(string fieldRef)
        {
            return new QueryBuilderOperator("IsNotNull", fieldRef);
        }

        /// <summary>
        /// Return a new expression that checks if a lookup field is not null.
        /// </summary>
        /// <param name="fieldRef">The name of the lookup field</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression IsNotNullLookup(string fieldRef)
        {
            return new QueryBuilderValueOperator<int>("Neq", fieldRef, 0, true);
        }

        /// <summary>
        /// Return a new expression that checks if a field is null.
        /// </summary>
        /// <remarks>
        /// If you want to check if a lookupfield is null use <see cref="IsNullLookup"/>.
        /// </remarks>
        /// <param name="fieldRef">The name of the field</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression IsNull(string fieldRef)
        {
            return new QueryBuilderOperator("IsNull", fieldRef);
        }

        /// <summary>
        /// Return a new expression that checks if a lookup field is null.
        /// </summary>
        /// <param name="fieldRef">The name of the lookup field</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression IsNullLookup(string fieldRef)
        {
            return new QueryBuilderValueOperator<int>("Eq", fieldRef, 0, true);
        }

        /// <summary>
        /// Returns a new expression that checks if the listitem is from a certain content type
        /// </summary>
        /// <param name="cTypeId">The content type</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression IsContentType(SPContentTypeId cTypeId)
        {
            return IsContentType(cTypeId.ToString());
        }
        /// <summary>
        /// Returns a new expression that orders the listitems
        /// </summary>
        /// <param name="fieldRef">Order by this field</param>
        /// <param name="ascending">Direction</param>
        /// <returns></returns>
        public static QueryBuilderOrderByOperator By(string fieldRef, bool ascending)
        {
            return new QueryBuilderOrderByOperator(fieldRef, ascending);
        }

        /// <summary>
        /// Returns a new expression that checks if the listitem is from a certain content type
        /// </summary>
        /// <param name="cTypeId">The content type</param>
        /// <returns>The expression</returns>
        public static IQueryBuilderExpression IsContentType(string cTypeId)
        {
            return BeginsWith("ContentTypeId", cTypeId);
        }

        /// <summary>
        /// Generates a xml-string in the format <c>&lt;FieldRef Name="..." /&gt;&lt;FieldRef Name="..." /&gt;...</c>.
        /// </summary>
        /// <param name="fields">The fieldnames to convert into fieldref tags</param>
        /// <returns>A xml-string with the fieldref tags</returns>
        public static string GetFieldRefCollection(params string[] fields)
        {
            return fields.Aggregate(new StringBuilder(), (sb, f) => sb.AppendFormat("<FieldRef Name=\"{0}\" />", f)).ToString();
        }

        /// <summary>
        /// Adds or replaces the fieldref tags from the query.
        /// </summary>
        /// <param name="query"></param>
        /// <param name="parentTag"></param>
        /// <param name="fieldRefs"></param>
        /// <returns></returns>
        private static SPQuery AddOrReplaceFieldRefs(SPQuery query, string parentTag, string fieldRefs)
        {
            string toAddOrReplace = string.IsNullOrEmpty(fieldRefs) ? "" : string.Format("<{0}>{1}</{0}>", parentTag, fieldRefs);
            var regex = new Regex(string.Format(@"\<{0}\>.*\</{0}\>", parentTag), RegexOptions.IgnoreCase);
            if (query.Query != null && regex.IsMatch(query.Query))
            {
                query.Query = regex.Replace(query.Query, toAddOrReplace);
            }
            else
            {
                query.Query += toAddOrReplace;
            }
            return query;
        }
    }
}
